/**
 * \file Descriptor.cc
 * \brief x86 descriptor tables
 * \author Corey Tabaka
 */
 
/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <platform/Descriptor.h>
#include <platform/processor.h>
#include <Sync.h>
#include <i386/mem.h>
#include <stdio.h>

/*
 * Global Descriptor Table (GDT)
 */
static seg_desc_t sys_gdt[1024];
static SMPLOCK(SpinLock) gdtLock;

/*
 * Interrupt Descriptor Table (IDT)
 */
static int_desc_t sys_idt[256];
static SMPLOCK(SpinLock) idtLock;

extern seg_desc_t gdt[];
extern seg_desc_t idt[];

extern "C" {
void loadGDTR(seg_desc_t * gdt, uint32 limit);
void loadIDTR(int_desc_t * idt, uint32 limit);
void loadDS(seg_sel_t ds);
}

void Descriptor::initialize(void) {
	// setup the system GDT and copy the boot GDT into it
	memset(sys_gdt, 0, sizeof(sys_gdt));
	memcpy(sys_gdt, gdt, sizeof(seg_desc_t)*6);
	loadGDTR(sys_gdt, sizeof(sys_gdt)-1);
	
	// mark free entries
	for (uint32 i=SEG_SYSTEM_RESERVED; i < sizeof(sys_gdt)/sizeof(seg_desc_t);
		i++) {
		sys_gdt[i].avl = 1;
	}
	
	// setup the system IDT and copy the boot IDT into it
	memset(sys_idt, 0, sizeof(sys_idt));
	memcpy(sys_idt, idt, sizeof(int_desc_t) * 0x32);
	loadIDTR(sys_idt, sizeof(sys_idt)-1);
	loadDS(DATA_SELECTOR);
}

/*
 * Descriptor management
 */

seg_sel_t Descriptor::allocGlobalDescriptor(void) {
	gdtLock.lock();
	
	seg_sel_t res = 0;
	
	for (uint32 i = SEG_SYSTEM_RESERVED; i < sizeof(sys_gdt)/sizeof(seg_desc_t);
		i++) {
		if (sys_gdt[i].avl == 1) {
			sys_gdt[i].avl = 0;
			
			res = (seg_sel_t) i << 3;
			break;
		}
	}
	
	gdtLock.unlock();
	return res;
}

void Descriptor::freeGlobalDescriptor(seg_sel_t sel) {
	gdtLock.lock();
	
	uint32 i = sel >> 3;
	if ((sel & 0x4) == 0 && i >= SEG_SYSTEM_RESERVED) {
		memset(sys_gdt + i, 0, sizeof(seg_desc_t));
		sys_gdt[i].avl = 1;
	}
	
	gdtLock.unlock();
}

seg_desc_t *Descriptor::getGlobalDescriptor(seg_sel_t sel) {
	uint32 i = sel >> 3;
	return &sys_gdt[i];
}

void Descriptor::setGlobalDescriptor(seg_sel_t sel, void *base, uint32 size,
	uint8 present, uint8 ring, uint8 sys, uint8 type, uint8 gran, uint8 bits) {
	// convert selector into index
	uint16 index = sel >> 3;
	
	sys_gdt[index].limit_15_0 = size & 0x0000ffff;
	sys_gdt[index].limit_19_16 = (size & 0x000f0000) >> 16;
	
	sys_gdt[index].base_15_0 = ((uint32) base) & 0x0000ffff;
	sys_gdt[index].base_23_16 = (((uint32) base) & 0x00ff0000) >> 16;
	sys_gdt[index].base_31_24 = ((uint32) base) >> 24;
	
	sys_gdt[index].type = type & 0x0f;	// segment type
	sys_gdt[index].p = present != 0;	// present
	sys_gdt[index].dpl = ring & 0x03;	// descriptor privilege level
	sys_gdt[index].g = gran != 0;		// granularity
	sys_gdt[index].s = sys != 0;		// system / non-system
	sys_gdt[index].d_b = bits != 0;		// 16 / 32 bit
}

void Descriptor::setTaskGate(uint8 interrupt, seg_sel_t sel, uint8 dpl, uint8 pres) {
	sys_idt[interrupt].task.selector = sel;
	
	sys_idt[interrupt].task.type = SEG_TYPE_TASK_GATE;	// segment type
	sys_idt[interrupt].task.p = pres != 0;				// present
	sys_idt[interrupt].task.dpl = dpl & 0x03;			// descriptor privilege
}

void Descriptor::setIntGate(uint8 interrupt, void *offset, uint8 dpl, uint8 pres) {
	sys_idt[interrupt].interrupt.offset_15_0 =
		((uint32) offset & 0x0000ffff);
	sys_idt[interrupt].interrupt.offset_31_16 =
		((uint32) offset & 0xffff0000) >> 16;
	
	sys_idt[interrupt].interrupt.type = SEG_TYPE_INT_GATE;	// segment type
	sys_idt[interrupt].interrupt.p = pres != 0;				// present
	sys_idt[interrupt].interrupt.dpl = dpl & 0x03;			// dpl
}

/*
 * Descriptor utilities
 */
uint32 DTE_limit(seg_desc_t *dt, uint32 i) {
	return dt[i].limit_19_16 << 16 | dt[i].limit_15_0;
}

uint32 DTE_base(seg_desc_t *dt, uint32 i) {
	return dt[i].base_31_24 << 24 | dt[i].base_23_16 << 16 | dt[i].base_15_0;
}

uint8 DTE_seg_type(seg_desc_t *dt, uint32 i) {
	return dt[i].type;
}

uint8 DTE_desc_type(seg_desc_t *dt, uint32 i) {
	return dt[i].s;
}

uint8 DTE_desc_priv(seg_desc_t *dt, uint32 i) {
	return dt[i].dpl;
}

uint8 DTE_seg_present(seg_desc_t *dt, uint32 i) {
	return dt[i].p;
}

uint8 DTE_available(seg_desc_t *dt, uint32 i) {
	return dt[i].avl;
}

uint8 DTE_op_size(seg_desc_t *dt, uint32 i) {
	return dt[i].d_b;
}

uint8 DTE_granularity(seg_desc_t *dt, uint32 i) {
	return dt[i].g;
}
